/**
 * 版权所有(C)，上海勾芒信息科技，2017，所有权利保留。
 * 
 * 项目名：	cards-web
 * 文件名：	WxAppServiceImpl.java
 * 模块说明：	
 * 修改历史：
 * 2017年4月21日 - Debenson - 创建。
 */
package me.chanjar.weixin.component.api.impl;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.concurrent.locks.Lock;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpHost;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.CloseableHttpClient;

import com.gomore.jeecg.common.Assert;
import com.google.common.collect.Maps;

import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import me.chanjar.weixin.common.WxNetworkingSupport;
import me.chanjar.weixin.common.bean.result.WxError;
import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.common.util.JsonMapper;
import me.chanjar.weixin.common.util.URL;
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder;
import me.chanjar.weixin.component.api.WxCompAuthorService;
import me.chanjar.weixin.component.api.WxCompConfigStorage;
import me.chanjar.weixin.component.api.WxCompService;
import me.chanjar.weixin.component.api.app.WxCompMaServiceDelegate;
import me.chanjar.weixin.component.api.app.impl.WxCompMaServiceDelegateImpl;
import me.chanjar.weixin.component.bean.WxComponentAccessToken;
import me.chanjar.weixin.component.bean.author.WxAuthorizerAccessToken;

/**
 * @author Debenson
 * @since 0.1
 */
public class WxCompServiceImpl extends WxNetworkingSupport implements WxCompService {
  private static final String ACCESS_TOKEN = "component_access_token";

  private CloseableHttpClient httpClient;
  private HttpHost httpProxy;

  private WxCompConfigStorage configStorage;
  private final WxCompAuthorService wxCompAuthorService = new WxCompAuthorServiceImpl(this);
  private final WxCompMaServiceDelegate maServiceDelegate = new WxCompMaServiceDelegateImpl(this);

  @Override
  public String getAccessToken() throws WxErrorException {
    return getAccessToken(false);
  }

  @Override
  public String getAccessToken(boolean forceRefresh) throws WxErrorException {
    Lock lock = getConfigStorage().getAccessTokenLock();
    try {
      lock.lock();

      if (forceRefresh) {
        getConfigStorage().expireAccessToken();
      }

      if (getConfigStorage().isAccessTokenExpired()) {
        WxComponentAccessToken token = retrieveAccessToken();
        getConfigStorage().updateAccessToken(token);
      }
    } finally {
      lock.unlock();
    }
    return getConfigStorage().getAccessToken();
  }

  private WxComponentAccessToken retrieveAccessToken() throws WxErrorException {
    final String url = "https://api.weixin.qq.com/cgi-bin/component/api_component_token";

    HttpPost httpPost = new HttpPost(url);
    httpPost.addHeader("Content-type", "application/json; charset=utf-8");
    httpPost.setHeader("Accept", "application/json");
    Map<String, Object> object = Maps.newHashMap();
    object.put("component_appid", configStorage.getAppId());
    object.put("component_appsecret", configStorage.getSecret());
    object.put("component_verify_ticket", configStorage.getVerifyTicket());
    String postBody = JsonMapper.defaultMapper().toJson(object);
    httpPost.setEntity(new StringEntity(postBody, Charset.forName("UTF-8")));

    if (getRequestHttpProxy() != null) {
      RequestConfig config = RequestConfig.custom().setProxy(getRequestHttpProxy()).build();
      httpPost.setConfig(config);
    }

    try {
      CloseableHttpResponse response = getRequestHttpClient().execute(httpPost);
      String resultContent = new BasicResponseHandler().handleResponse(response);
      WxError error = WxError.fromJson(resultContent);
      if (error.getErrorCode() != 0) {
        throw new WxErrorException(error);
      }

      WxComponentAccessToken accessToken = JsonMapper.defaultMapper().fromJson(resultContent,
          WxComponentAccessToken.class);
      return accessToken;
    } catch (IOException e) {
      throw new RuntimeException(e);
    } finally {
      httpPost.releaseConnection();
    }
  }

  @Override
  public String appendAccessToken(String url) throws WxErrorException {
    URL urlMapper = new URL(url);
    if (urlMapper.getParameter(ACCESS_TOKEN, null) != null) {
      throw new IllegalArgumentException("uri参数中不允许有" + ACCESS_TOKEN + ": " + url);
    }

    final String token = getAccessToken();
    urlMapper.addParameter(ACCESS_TOKEN, token);
    return urlMapper.toString();
  }

  @Override
  public boolean expireIfInvalidToken(int code) {
    boolean result = code == 42001 || code == 40001 || code == 40014;
    if (result) {
      getConfigStorage().expireAccessToken();// 强制设置为无效
    }
    return result;
  }

  @Override
  public WxCompConfigStorage getConfigStorage() {
    return configStorage;
  }

  @Override
  public void setConfigStorage(WxCompConfigStorage wxComponentConfigProvider) {
    this.configStorage = wxComponentConfigProvider;
    this.initHttpClient();
  }

  private void initHttpClient() {
    ApacheHttpClientBuilder apacheHttpClientBuilder = DefaultApacheHttpClientBuilder.get();
    httpClient = apacheHttpClientBuilder.build();
    httpProxy = null;
  }

  @Override
  public String getAuthorizerAccessToken(String appId) throws WxErrorException {
    return getAuthorizerAccessToken(appId, false);
  }

  @Override
  public String getAuthorizerAccessToken(String appId, boolean forceRefresh)
      throws WxErrorException {
    Lock lock = getConfigStorage().getAuthorizerAccessTokenLock();
    try {
      lock.lock();

      if (forceRefresh) {
        getConfigStorage().expireAuthorizerAccessToken(appId);
      }

      if (getConfigStorage().isAuthorizerAccessTokenExpired(appId)) {
        String refreshToken = getConfigStorage().getAuthorizerRefreshToken(appId);
        if (StringUtils.isBlank(refreshToken)) {
          throw new RuntimeException("没有取得接口调用凭据刷新令牌，不能刷新授权方接口调用凭据，appId=" + appId);
        }

        // 刷新授权方接口调用凭据
        WxAuthorizerAccessToken authorToken = getAuthorService().getAuthorizerAccessToken(appId,
            refreshToken);
        getConfigStorage().updateAuthorizerAccessToken(appId, authorToken);
      }
    } finally {
      lock.unlock();
    }
    return getConfigStorage().getAuthorizerAccessToken(appId);
  }

  @Override
  public WxCompAuthorService getAuthorService() {
    return wxCompAuthorService;
  }

  @Override
  public WxCompMaServiceDelegate getMaServiceDelegate() {
    return this.maServiceDelegate;
  }

  @Override
  public CloseableHttpClient getRequestHttpClient() {
    return httpClient;
  }

  @Override
  public HttpHost getRequestHttpProxy() {
    return httpProxy;
  }

  @Override
  public WxMaJscode2SessionResult getSessionInfo(String code, String appId)
      throws WxErrorException {

    Assert.assertArgumentNotEmpty(code, "code");
    Assert.assertArgumentNotEmpty(appId, "appId");

    final String url = "https://api.weixin.qq.com/sns/component/jscode2session";
    Map<String, Object> params = Maps.newHashMap();
    params.put("appid", appId);
    params.put("js_code", code);
    params.put("grant_type", "authorization_code");
    params.put("component_appid", configStorage.getAppId());
    return WxMaJscode2SessionResult.fromJson(httpGet(url, params));
  }

}
